
import { Reflector } from '@nestjs/core';
import {ClassDeclaration, Decorator, EnumDeclaration, Expression, InterfaceDeclaration, MethodDeclaration, ObjectLiteralExpression, Project, SourceFile, SyntaxKind, Type, TypeAliasDeclaration, TypeReferenceNode} from 'ts-morph'
import * as path from "path";

class XbzSdk {
    private reflector: Reflector;
    private project:Project;
    private controllers:Project;
    private rootPath:string
    constructor() {
        this.reflector = new Reflector();
        this.project=new Project({tsConfigFilePath:"tsconfig.json"})
        this.rootPath=this.project.getCompilerOptions().baseUrl!+'/src'
        console.log("this.rootPath", this.rootPath)
        this.controllers=new Project()
    }
    async scan() {
        console.time("start")
        this.project.getSourceFiles().forEach(sourceFile => {
            //筛选出含有@Controller装饰器的文件
            const controllerDecorators = sourceFile.getDescendantsOfKind(SyntaxKind.Decorator).filter(decorator => {
                return decorator.getName() === "Controller";
            });
            if(controllerDecorators.length > 0){
                this.controllers.addSourceFileAtPath(sourceFile.getFilePath())
            }
        })
        let controllerInfos:any[]=[]
        this.controllers.getSourceFiles().forEach(sourceFile => {
            let arr=this.parseSourceFile(sourceFile)
            controllerInfos.push(...arr)
        })
        const groupedControllerInfos = controllerInfos.reduce((acc, controllerInfo) => {
            const { modulePath } = controllerInfo;
            if (!acc[modulePath]) {
                acc[modulePath] = [];
            }
            acc[modulePath].push(controllerInfo);
            return acc;
        }, {});

        Object.keys(groupedControllerInfos).forEach(modulePath => {
            this.generateFrontendFile(modulePath, groupedControllerInfos[modulePath])
        })

        // console.log("groupedControllerInfos", groupedControllerInfos);
        // console.log("controllerInfos",controllerInfos)
        console.timeEnd("start")
        // await applicationContext.close();
    }

    /**解析文件 */
    private parseSourceFile(sourceFile:SourceFile){
        let controllerInfos:any[]=[]
        sourceFile.getClasses().forEach(classNode => {
            let controllerInfo=this.parseController(classNode)
            if(controllerInfo){
                controllerInfos.push(controllerInfo)
            }
        })
        return controllerInfos
    }

    /**解析控制器 */
    private parseController(classNode: ClassDeclaration){
        let controllerDecorator = classNode.getDecorator("Controller")
        if (!controllerDecorator) {
            return
        }
        let controllerDecoArgs = this.parseDecoratorArgs(controllerDecorator)
        let routeKey = "/"
        if (controllerDecoArgs.length > 0) {
            routeKey = '/' + controllerDecoArgs[0].value
        }
        // if(!routeKey.startsWith("/app")){
        //    return
        // }
        let filePath=classNode.getSourceFile().getFilePath().replace(this.rootPath+path.sep, "")
        let modulePath = filePath.split(path.sep).slice(0, -1).join(path.sep);
        let controllerInfo={
            filePath,
            modulePath,
            routeKey,
            methods:[] as any[]
        }
        classNode.getMethods().forEach(methodNode => {
            let methodInfo=this.parseMethod(methodNode)
            controllerInfo.methods.push(methodInfo)
        })
        return controllerInfo
    }

    /**解析方法 */
    private parseMethod(methodNode: MethodDeclaration){
        // let methodDecorator = methodNode.getDecorator("SDK")
        // if (!methodDecorator) {
        //     return
        // }
        let restfulDecorators = new Set(["Get", "Post", "Put", "Delete", "Patch"]);
        let decorators = methodNode.getDecorators();
        let restfulDecorator = decorators.find(decorator => restfulDecorators.has(decorator.getName()));
        //没有restful装饰器的方法不处理
        if (!restfulDecorator) {
            return
        }
        //获取请求方法
        let requestMethod = restfulDecorator.getName().toLowerCase();
        //获取请求路径
        let routeKey = ""
        let pathArgs = this.parseDecoratorArgs(restfulDecorator)
        if (pathArgs.length > 0) {
            routeKey = pathArgs[0].value
        }
        routeKey=routeKey.replace(/\\/g, path.sep)
        let params:any[]=[]
        methodNode.getParameters().forEach(param=>{
            let paramDecorator=param.getDecorators()[0].getName()
            let paramType=this.parseType(param.getType())
            if(["Param","Body","Query"].includes(paramDecorator)){
                params.push({
                    name:param.getName(),
                    type:paramType.type,
                    importPath:paramType.importPath,
                    paramDecorator
                })
            }
        })
        let returnType=this.parseType(methodNode.getReturnType())

        //获取方法的注释
        let jsDocs = methodNode.getJsDocs().map(jsDoc => jsDoc.getStructure());
        return {
            methodName:methodNode.getName(),
            requestMethod,
            routeKey,
            returnType,
            params,
            jsDocs,
        }
    }

     /**获取装饰器参数 */
    private parseDecoratorArgs(decorator: Decorator,sourceFile?:SourceFile): {
        kind: string,
        value: any,
		importPath?: string,
    }[] {
        const args = decorator.getArguments();
        const result: any[] = [];
        args.forEach(arg => {
            if (arg.getKind() === SyntaxKind.StringLiteral) {
                result.push({
                    kind: 'string',
                    value: (arg as Expression).getText().slice(1, -1), // 去掉首尾引号
                });
            } else if (arg.getKind() === SyntaxKind.ObjectLiteralExpression) {
                const obj = arg as ObjectLiteralExpression;
                const objResult: Record<string, any> = {};

                obj.getProperties().forEach(prop => {
                    if (prop.getKind() == SyntaxKind.PropertyAssignment) {
                        //@ts-ignore
                        const name = prop.getName();
                        //@ts-ignore
                        const initializer = prop.getInitializer();

                        if (initializer) {
                            if (initializer.getKind() === SyntaxKind.StringLiteral) {
                                objResult[name] = initializer.getText().slice(1, -1); // 去掉首尾引号
                            } else {
                                objResult[name] = initializer.getText();
                            }
                        }
                    }
                });

                result.push({
                    kind: 'object',
                    value: objResult,
                });
            }else if (arg.getKind() === SyntaxKind.TrueKeyword) {
				result.push({
					kind: 'boolean',
					value: true,
				});
			}
			else if (arg.getKind() === SyntaxKind.Identifier) {
				let importPath=""
				if(sourceFile){
					let identifierName=arg.getText()
					const importDeclaration = sourceFile.getImportDeclarations().find(importDec =>
						importDec.getNamedImports().some(namedImport => namedImport.getName() === identifierName)
					);
					if (importDeclaration) {
						importPath=importDeclaration.getModuleSpecifierValue()
						// importPath=path.resolve(this.srcPath,importPath)
					}
				}

				result.push({
					kind: 'identifier',
					value: arg.getText(),
					importPath,
				});
			}
        });

        return result;
    }

    /**解析参数描述 */
    private parseType(type:Type){
        if(type.getText().startsWith("import(")){
            let importPath=type.getText().match(/import\((.+)\)/)?.[1].slice(1, -1)
            let returnType=type.getText().match(/\)\.(.+)/)?.[1]
            return {
                type:returnType,
                importPath
            }
        }else if(type.getText().startsWith("Partial<")){
            const partialTypeMatch = type.getText().match(/Partial<(.+)>/);
            let innerType = partialTypeMatch![1];
            let importPath=innerType.match(/import\((.+)\)/)?.[1].slice(1, -1)
            let returnType=innerType.match(/\)\.(.+)/)?.[1]
            return {
                type:returnType,
                importPath
            }
        }else{
            return {
                type:type.getText(),
                importPath:null
            }
        }
    }

    /**按名称获取SourceFile内的成员 */
    private getMemberByName(sourceFile:SourceFile,name:string){
        return sourceFile.getClasses().find(classNode => classNode.getName() === name) ||
               sourceFile.getInterfaces().find(interfaceNode => interfaceNode.getName() === name) ||
               sourceFile.getTypeAliases().find(functionNode => functionNode.getName() === name)||
               sourceFile.getEnums().find(enumNode => enumNode.getName() === name)
    }

    /**根据模块生成前端ts文件 */
    private generateFrontendFile(modulePath:string,controllerInfos:any[]){
        let tempProject=new Project()
        controllerInfos.forEach(controllerInfo=>{
            let originalFileName=controllerInfo.filePath.split(path.sep).pop()!
            let name=originalFileName.split('.')[0]
            let tempFile=tempProject.createSourceFile(path.join('ts-sdk',modulePath,name+'.ts'),undefined,{overwrite:true})
            let importMapSet:Map<string,Set<string>>=new Map()
            controllerInfo.methods.forEach(methodInfo=>{
                methodInfo.params.forEach(param=>{
                    if(param.importPath){
                        if(!importMapSet.has(param.importPath)){
                            importMapSet.set(param.importPath,new Set())
                        }
                        importMapSet.get(param.importPath)!.add(param.type)
                    }
                }) 

                let fetchUrl = `${controllerInfo.routeKey}/${methodInfo.routeKey}`;
                if (!methodInfo.routeKey) {
                    fetchUrl = fetchUrl.replace(/\/$/, '');
                }

                let paramDecorator=methodInfo.params.find(item=>item.paramDecorator=='Param')
                if(paramDecorator&&fetchUrl.includes(`:${paramDecorator.name}`)){
                    fetchUrl=fetchUrl.replace(`:${paramDecorator.name}`,`\${${paramDecorator.name}}`)
                }

                let statements=`return axios.${methodInfo.requestMethod}(\`${fetchUrl}\``

                let bodyDecorator=methodInfo.params.find(item=>item.paramDecorator=='Body')
                if(bodyDecorator){
                    statements+=`,${bodyDecorator.name}`
                }

                let queryDecorator=methodInfo.params.find(item=>item.paramDecorator=='Query')
                if(queryDecorator){
                    statements+=`,{params:${queryDecorator.name}}`
                }

                statements+=`)`

                tempFile.addFunction({
                    docs:methodInfo.jsDocs,
                    isAsync:true,
                    isExported:true,
                    name:methodInfo.methodName,
                    parameters:methodInfo.params,
                    returnType:`Promise<${methodInfo.returnType.type}>`,
                    statements:statements
                })
            })

            let newImportMapSet:Map<string,Set<string>>=new Map()
            importMapSet.forEach((importSet,importPath)=>{
                let newTypeName=path.basename(importPath).split('.')[0]+'.d.ts'
                let typeFile=tempProject.createSourceFile(path.join('ts-sdk',modulePath,'types',newTypeName),undefined,{overwrite:true})
                newImportMapSet.set(newTypeName,importSet)
                this.generateTypeFile(typeFile,importPath,importSet)
            })
            newImportMapSet.forEach((importSet,importPath)=>{
                tempFile.addImportDeclaration({
                    isTypeOnly:true,
                    moduleSpecifier:'./types/'+importPath,
                    namedImports:Array.from(importSet)
                })
            })
           
            tempFile.saveSync()
            
        })
    }

    /**生成类型文件 */
    private generateTypeFile(typeFile:SourceFile,importPath:string,importSet:Set<string>){
        let tempProject=new Project()
        let targetFile=tempProject.addSourceFileAtPath(importPath+'.ts')
        let importMapSet:Map<string,Set<string>>=new Map()
        importSet.forEach(typeName=>{
            let typeNode=this.getMemberByName(targetFile,typeName)
            if(typeNode){
                //只处理接口和类
                if(typeNode instanceof InterfaceDeclaration||typeNode instanceof ClassDeclaration){
                    let extendsArr:any[]=typeNode.getBaseTypes().map(baseType=>this.parseType(baseType))
                
                    if(extendsArr.length>0){
                        for (const extend of extendsArr) {
                            let alreadyExist=this.getMemberByName(typeFile,extend.type)
                            if(!alreadyExist){
                                if(importMapSet.has(extend.importPath)){
                                    importMapSet.get(extend.importPath)!.add(extend.type)
                                }else{
                                    importMapSet.set(extend.importPath,new Set<string>().add(extend.type))
                                }
                            }
                        }
                    }

                    typeFile.addInterface({
                        isExported:true,
                        extends:extendsArr.map(extend=>extend.type),
                        name:typeName,
                        docs:typeNode.getJsDocs().map(jsDoc => jsDoc.getStructure()),
                        properties:typeNode.getProperties().map(property=>{
                            let parseType=this.parseType(property.getType())
                            if(parseType.importPath){
                                if(importMapSet.has(parseType.importPath)){
                                    importMapSet.get(parseType.importPath)!.add(parseType.type!)
                                }else{
                                    importMapSet.set(parseType.importPath,new Set<string>().add(parseType.type!))
                                }
                            }
                            return {
                                name:property.getName(),
                                type:parseType.type,
                                docs:property.getJsDocs().map(jsDoc => jsDoc.getStructure())
                            }
                        })
                    })
                }
                //处理type
                if(typeNode instanceof TypeAliasDeclaration){
                    typeFile.addTypeAlias(typeNode.getStructure())
                }

                //处理枚举
                if(typeNode instanceof EnumDeclaration){
                    typeFile.addEnum(typeNode.getStructure())
                }
            }
        })

        importMapSet.forEach((importSet,importPath)=>{
            this.generateTypeFile(typeFile,importPath,importSet)
        })

        typeFile.saveSync()
    }
}

new XbzSdk().scan();